import {
	FetchFilesystem,
	InMemoryFilesystem,
	OverlayFilesystem,
	ZipFilesystem,
} from '@wp-playground/storage';
import type { BlueprintBundle } from './types';

export class BlueprintFetchError extends Error {
	public readonly url: string;

	constructor(message: string, url: string, options?: ErrorOptions) {
		super(message, options);
		this.name = 'BlueprintFetchError';
		this.url = url;
	}
}

/**
 * Resolves a remote blueprint from a URL.
 *
 * @param url - The URL of the blueprint to resolve.
 * @returns A promise that resolves to the resolved blueprint.
 */
export async function resolveRemoteBlueprint(
	url: string
): Promise<BlueprintBundle> {
	let blueprintBytes: ArrayBuffer;
	try {
		const response = await fetch(url, {
			credentials: 'omit',
		});
		if (!response.ok) {
			throw new Error(`Failed to fetch blueprint from ${url}`);
		}
		blueprintBytes = await response.arrayBuffer();
	} catch (error) {
		throw new BlueprintFetchError(
			`Blueprint file could not be resolved from ${url}: ${error instanceof Error ? error.message : String(error)}`,
			url,
			{ cause: error }
		);
	}

	try {
		const blueprintText = new TextDecoder().decode(blueprintBytes);
		JSON.parse(blueprintText);

		// No exceptions, good! We're dealing with a JSON file. Let's
		// resolve the "bundled" resources from the same remote URL.
		return new OverlayFilesystem([
			new InMemoryFilesystem({
				'blueprint.json': blueprintText,
			}),
			new FetchFilesystem({
				baseUrl: url,
			}),
		]);
	} catch (error) {
		// If the blueprint is not a JSON file, check if it's a ZIP file.
		if (await looksLikeZipFile(blueprintBytes)) {
			return ZipFilesystem.fromArrayBuffer(blueprintBytes);
		}
		throw new Error(
			`Blueprint file at ${url} is neither a valid JSON nor a ZIP file.`,
			{ cause: error }
		);
	}
}

async function looksLikeZipFile(bytes: ArrayBuffer): Promise<boolean> {
	if (bytes.byteLength < 4) {
		return false;
	}
	const filePrefix = new Uint8Array(bytes, 0, 4);
	// Check against the signature for non-empty, non-spanned zip files.
	const matchesZipSignature =
		filePrefix[0] === 0x50 &&
		filePrefix[1] === 0x4b &&
		filePrefix[2] === 0x03 &&
		filePrefix[3] === 0x04;
	return matchesZipSignature;
}
